#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/tcp.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>
#include <rtc_base/logging.h>

#include "socket.h"

int create_udp_socket(int family) {
    int sock = socket(family, SOCK_DGRAM, 0);
    if (-1 == sock) {
        RTC_LOG(LS_WARNING) << "create udp socket error: " << strerror(errno)
            << ", errno: " << errno;
        return -1;
    }

    return sock;
}
int sock_setnoblock(int sock) {
    int flags = fcntl(sock, F_GETFL);
    if (-1 == flags) {
       RTC_LOG(LS_WARNING) << "fcntl F_GETFL error:" << strerror(errno) << ", errno:" << errno << ", fd:" << sock;
       return -1;
    }

    if (-1 == fcntl(sock, F_SETFL, flags | O_NONBLOCK)) {
        RTC_LOG(LS_WARNING) << "fcntl set nonblock error:" << strerror(errno) << ", errno:" << errno << ", fd:" << sock;
        return -1;   
    }

    return 0;
}

int sock_setnodelay(int sock) {
    int on = 1;
    int ret = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
    if (-1 == ret) {
       RTC_LOG(LS_WARNING) << "set nodeley error:" << strerror(errno) << ", errno:" << errno << ", fd:" << sock;
       return -1;
    }

    return 0;
}

int sock_peer_to_str(int sock, char *ip, int *port) {
    struct sockaddr_in sa;
    socklen_t salen;

    int ret = getpeername(sock, (struct sockaddr*)&sa, &salen);
    if (-1 == ret) {
        if (ip) {
            ip[0] = '?';
            ip[1] = '\0';
        }

        if (port) {
            *port = 0;
        }

        return -1;
    }

    if (ip) {
        strcpy(ip, inet_ntoa(sa.sin_addr));
    }

    if (port) {
        *port = ntohs(sa.sin_port);
    }

    return 0;
}
int sock_bind(int sock, struct sockaddr* addr, socklen_t len, int min_port, int max_port) {
    int ret = -1;
    if (0 == min_port && 0 == max_port) {
        // 让操作系统自动选择一个port
        ret = bind(sock, addr, len);
    } else {
        struct sockaddr_in* addr_in = (struct sockaddr_in*)addr;
        for (int port = min_port; port <= max_port && ret != 0; ++port) {
            addr_in->sin_port = htons(port);
            ret = bind(sock, addr, len);
        }
    }

    if (ret != 0) {
        RTC_LOG(LS_WARNING) << "sock bind error: " << strerror(errno) << ", errno: " << errno;
    }

    return ret;    
}

int sock_get_address(int sock, char* ip, int* port) {
    struct sockaddr_in addr_in;
    socklen_t len = sizeof(sockaddr);
    int ret = getsockname(sock, (struct sockaddr*)&addr_in, &len);
    if (ret != 0) {
        RTC_LOG(LS_WARNING) << "getsockname error: " << strerror(errno) << 
            ", errno: " << errno;
        return -1;
    }

    if (ip) {
        strcpy(ip, inet_ntoa(addr_in.sin_addr));
    }

    if (port) {
        *port = ntohs(addr_in.sin_port);
    }

    return 0;
}

int sock_recv_from(int sock, char* buf, size_t len, struct sockaddr* addr, socklen_t addr_len) {
    int received = recvfrom(sock, buf, len, 0, addr, &addr_len);
    if (received < 0) {
        if (EAGAIN == errno) {
            received = 0;
        } else {
            RTC_LOG(LS_WARNING) << "recv from error: " << strerror(errno) 
                << ", errno: " << errno;
            return -1;
        }
    } else if (0 == received) {
        RTC_LOG(LS_WARNING) << "recv 0 bytes, error: " << strerror(errno) 
            << ", errno: " << errno;
        return -1;
    }

    return received;    
}

int64_t sock_get_recv_timestamp(int sock) {
    struct timeval time;
    int ret = ioctl(sock, SIOCGSTAMP, &time);
    if (ret != 0) {
        return -1;
    }

    return time.tv_sec * 1000000 + time.tv_usec;
}
int sock_send_to(int sock, const char* buf, size_t len, int flag,
        struct sockaddr* addr, socklen_t addr_len)
{
    int sent = sendto(sock, buf, len, flag, addr, addr_len);
    if (sent < 0) {
        if (EAGAIN == errno) {
            sent = 0;
        } else {
            RTC_LOG(LS_WARNING) << "sendto error: " << strerror(errno) << ", errno: " << errno;
            return -1;
        }
    } else if (0 == sent) {
        RTC_LOG(LS_WARNING) << "sendto error: " << strerror(errno) << ", errno: " << errno;
        return -1;
    }

    return sent;
}